package com.kk.algorithm.prim;

import java.util.Arrays;
/*
 * @Description:    普利姆算法实现
 * @Author:         阿K
 * @CreateDate:     2021/2/8 12:31
 * @Param:
 * @Return:
**/
public class PrimAlgorithm {
    public static void main(String[] args) {
        // 测试图创建
        char[] data = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        int verxs = data.length;
        //邻接矩阵的关系使用二维数组表示,10000这个大数，表示两个点不联通
        int[][] weight = new int[][]{
                {10000, 5, 7, 10000, 10000, 10000, 2},
                {5, 10000, 10000, 9, 10000, 10000, 3},
                {7, 10000, 10000, 10000, 8, 10000, 10000},
                {10000, 9, 10000, 10000, 10000, 4, 10000},
                {10000, 10000, 8, 10000, 10000, 5, 4},
                {10000, 10000, 10000, 4, 5, 10000, 6},
                {2, 3, 10000, 10000, 4, 6, 10000},};

        //创建MGraph对象
        MGraph graph = new MGraph (verxs);
        //创建一个MinTree对象
        MinTree minTree = new MinTree ( );
        minTree.createGraph (graph, verxs, data, weight);
        //输出
        minTree.showGraph (graph);
        //测试普利姆算法
        minTree.prim (graph, 0);
    }


}


// 构建最小生成树对象 --- 村庄图
class MinTree {

    /**
     * 构建图的邻接矩阵(初始化)
     *
     * @param graph  图对象
     * @param verxs  图对应的顶点个数
     * @param data   图的各个顶点编号，这里不是权值（编号作为表示，不用于计算；权值通常用于计算，个人理解--阿K）
     * @param weight 图的邻接矩阵
     */
    public void createGraph(MGraph graph, int verxs, char[] data, int[][] weight) {

        for (int i = 0; i < verxs; i++) {// 遍历各个顶点
            graph.data[i] = data[i];
            for (int j = 0; j < verxs; j++) {
                graph.weight[i][j] = weight[i][j];
            }
        }
    }

    // 显示图的邻接矩阵
    public void showGraph(MGraph graph) {
        for (int[] link : graph.weight) {
            System.out.println (Arrays.toString (link));
        }
    }

    /**
     * @param graph 图
     * @param v     表示从图的第几个顶点开始生成 'A'-0,'B'-1 ...
     */
    public void prim(MGraph graph, int v) {
        // visited[] 标记节点(顶点)是否被访问过
        int[] visited = new int[graph.verxs];

        // 初始化，默认 0 是未访问过，可以不写，但是我愿意！
        for (int i = 0; i < visited.length; i++) {
            visited[i] = 0;
        }

        // 把当前节点标记为已访问过
        visited[v] = 1;
        // h1 and h2 record double node of subscript(邻接矩阵是二位数组，对应着 double subscript)
        int h1 = -1;
        int h2 = -1;
        // 将 minWeight 初始化成大数 10000，后面遍历过程中会被替换（为什么初始化成大数，因为这样默认是走不通的 根据案例设计）
        int minWeight = 10000;// 边（最小权值）

        // 核心部分：
        for (int k = 1; k < graph.verxs; k++) {// 公式中 顶点个数为n ，边为 n-1，所以 从 1开始

            // 该双层循环作用：用于确定每一次生成的子图，和哪个节点的距离最近
            // 子图：就是图解上的步骤 1 - 6 中， 1 是 A-C [7]， A-G[2] ，A-B[5] ， 2 是 A-C[7] ，A-B[5] ， G-B[3] ，G-E[4] ，G-F[6] ......
            // 其实就是对图遍历两遍，一遍访问过的，一遍没访问过的，有访问过的根据没访问过的计算权值(边)，得出最小，然后标记为已经访问过，继续循环 k 层
            for (int i = 0; i < graph.verxs; i++) {// i 索引对应的节点表示 被访问过的节点，标识为 0
                for (int j = 0; j < graph.verxs; j++) {// j 索引对应的节点表示 未被访问过的节点，标识为 1
                    if (visited[i] == 1 && visited[j] == 0 && graph.weight[i][j] < minWeight) {// 当前节点的权值(边)小于最小节点的权值（边）
                        // 替换 minWeight(寻找已经访问过的结点和未访问过的结点间的权值最小的边)
                        minWeight = graph.weight[i][j];
                        h1 = i;
                        h2 = j;
                    }
                }
            }
            // 找到了一条边，是最小的
            System.out.println ("边<" + graph.data[h1] + "," + graph.data[h2] + "> 权值:" + minWeight);
            // 将当前节点标记为已经访问
            visited[h2] = 1;// 为什么 h1 不用置为已经标记？ 因为h1 本身已经是标记好的用来筛选，所以没必要
            // 重新设置为最大值
            minWeight = 10000;
        }
    }
}


// 图对象
class MGraph {
    int verxs;     // 表示图中节点的个数
    char[] data;   // 存放节点的数据
    int[][] weight;// 存放边（既 邻接矩阵）

    public MGraph(int verxs) {
        this.verxs = verxs;
        data = new char[verxs];
        weight = new int[verxs][verxs];
    }
}
